package funcs

import (
	"bufio"
	"bytes"
	log "github.com/YoungPioneers/blog4go"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

/**
该函数主要是通过showstatus函数执行后的结果进行过滤检查
ssout：是ss -an即showstatus的结果
concurrency_limit：即并发链接数
bad_ip_list：即阻止的IP地址列表，暂时可以理解为黑名单
ipset_blacklist:linux系统ipset中list的名字




 */
func CC_check(ssout string, concurrency_limit int, black_ip_list string, ipset_black_list_name string) {
	//showstatus函数执行的结果是ss -an后的结果，结果类似：
	/*	136 10.0.10.111
		14 101.201.149.49
		10 58.246.220.31
		5 10.0.10.115
		4 140.207.119.12
		2 10.0.3.7
		2 10.0.3.6
		2 10.0.3.5
		2 10.0.1.66
		1 10.0.3.8*/

	//打开已经阻止的IP列表，稍后准备将已经达到并发限制的IP地址写入该文件，目的是为了方便查看哪些IP被阻止了。
	f, err := os.OpenFile(black_ip_list, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		log.Error(err.Error())
	}
	defer f.Close()
	//out1 := ShowStatus("lal")
	//fmt.Println(out1)
	//将这些结果进行切片放到out2中，结果类似：
	/*[    134 10.0.10.111      14 101.201.149.49      10 58.246.220.31       5 10.0.10.115       4 140.207.119.12       2 10.0.3.7       2 10.0.3.6       2 10.0.3.5       2 10.0.1.66       1 10.0.3.8 ]
	 */
	// 将ssout的结果字符串按照行"\n"来进行切片
	ssout_slice := strings.Split(ssout, "\n")

	var buf bytes.Buffer
	w := bufio.NewWriter(f)

	//对切片进行遍历
	for _, value := range ssout_slice {

		//先将结果两端的空格去掉
		value_trim := strings.TrimSpace(value)
		//判断该值是否为空，如果是非空才继续进行
		if len(value_trim) != 0 {
			//因为这是的value是包含TCP IP的信息，所以需要再次切片处理
			//再处理后的结果再次切片,按空格切片
			value_trim_slice := strings.Split(value_trim, " ")
			//fmt.Println(out4[0])
			//fmt.Println(strings.TrimSpace(out4[0]))
			//这个切片的第一个元素是TCP连接数
			tcps, err := strconv.Atoi(strings.TrimSpace(value_trim_slice[0]))
			//这个切片的第二个元素就是我们要阻止的IP地址
			bad_ip := strings.TrimSpace(value_trim_slice[1])
			if err != nil {
				log.Error(err.Error())
			}
			//如果这个连接数大于或等于我们设置的并发数，则准备拉黑
			if tcps >= concurrency_limit {
				//1）
				//拉黑前我们先检查一下该IP地址是否已经存在于IPSET LIST中。
				//注意：因为IP地址虽然被阻止后，但是其通过ss -an检查到的结果不一定立刻降低到并发连接的下限。
				//所以这里检查一下该IP，然后再决定是否执行ipset add操作。
				//之前也考虑过以另一种方式来进行检测：读取black_ip_list获取已阻止的IP，再进行showstatus时进行过滤，这样就不需要再这里每次都进行检测了。
				//然而这种方式在black_ip_list内容较小时还行，猜想文件较大时可能会比较占用内存。(且因为读取的IP地址最终要拼接成字符串，最后交由
				// linux系统的ss -an | grep去过滤，如果是一个上千、上万、甚至百万级别的字符串，所以就先老老实实的用这种方式吧)

				//组装ipset检查命令 ipset test blacklist_godeny 1.2.3.2
				buf.Reset()
				buf.WriteString(`ipset test `)
				buf.WriteString(ipset_black_list_name)
				buf.WriteString(" ")
				buf.WriteString(bad_ip)
				cmd := exec.Command("/bin/bash", "-c", buf.String())
				_, err2 := cmd.CombinedOutput()
				if err2 != nil {
					//如果err1不等于nil，就是发生了错误，则相当于检测的IP地址还没有在IPSET LIST，但是该IP的并发连接数
					//已经超标，所以我们要禁止该IP
					log.Debug(bad_ip, " 经过ipset test检测不在IPSET LIST中，且并发连接超标，现在可以进行ipset add操作")

				} else {
					//	否则该IP已经存在于IPSET LIST中了，所以要跳过，进行下一个IP的检测。
					log.Debug(bad_ip, " 经过ipset test检测存在IPSET LIST中，进行下个ip的检测")
					continue
				}

				//2)
				// 将要拉黑的IP地址添加到IPSET_LIST中。
				err := IPsetAdd(ipset_black_list_name, bad_ip)
				if err != nil {
					log.Error("ipset add ", ipset_black_list_name, " ", bad_ip, "错误：", err.Error())
					//如果添加到IPSET错误，那么就跳出该循环，且打印错误日志
					continue
				}
				//log.Info(bad_ip, "\t连接数为:", tcps, "\t超过了并发连接数:", concurrency_limit, ",已被禁止！")
				log.Info(bad_ip, " connections is ", tcps, ",it more than ", concurrency_limit, ". The IP was blocked")
				//如果IPSET ADD添加成功，则继续将该IP地址添加到IP阻止列表中。
				//-----------------------------------------------------------------------------------------------------
				//3)
				// 将已经拉黑的IP地址添加到IP阻止列表中。
				buf.Reset()
				buf.WriteString(bad_ip)
				buf.WriteString("\t|\t")
				buf.WriteString(time.Now().Format("2006-01-02 15:04:05"))
				buf.WriteString("\n")
				_, err1 := w.WriteString(buf.String())
				if err1 != nil {
					//fmt.Println(err.Error())
					log.Error(bad_ip, " 添加到IP阻止列表时发生错误,err.Error(): ", err.Error())
					//	注意，如果我们往阻止IP列表中记录bad_ip时，如果添加失败，那么我们以后就无法将这个IP从
					//IPSET LIST中清除了，那么这个IP会一直在阻止状态。
					//所以这个时候要将这个IP从IPSET LIST中清除，反正记录了错误日志了，没事看看错误日志就好了嘛
					err2 := IPsetDel(ipset_black_list_name, bad_ip)
					//如果从IPSET LIST中清除该IP失败则记录日志
					if err2 != nil {
						log.Error(bad_ip, " 在记录到bad_ip_list时失败，且在清除IPSET LIST时也失败了,err.Error(): ", err.Error())
					}
				}
				w.Flush()
				//log.Info(bad_ip, "\t连接数为:", tcps, " 超过了并发连接数:", concurrency_limit, ",已成功添加到BLACK_IP_LIST~~~")
				log.Info(bad_ip, " connections is ", tcps, ",it more than ", concurrency_limit, ". The IP was write in BLACK_IP_LIST")

			}
		}

	}

}
